import threading, subprocess, traceback, time
import os
import constants
import utils
from config import config

class KsmMonitorThread(threading.Thread):
    def __init__(self, cif):
        threading.Thread.__init__(self, name = 'KsmMonitor')
        self.setDaemon(True)
        self._cif = cif
        self._prog = config.get('ksm', 'ksm_prog')
        try:
            memTotal = utils.readTotalFreeCachedMem()[0]
            self._sleep = str(config.getint('ksm', 'ksm_sleep') * 16 * 2**20 \
                                                                / memTotal)
        except:
            self._sleep = '10000'
        self.state, self.pages = False, 0
        self._lock = threading.Lock()
        if config.getboolean('ksm', 'ksm_monitor_thread') and \
           config.getboolean('ksm', 'ksm_enable'):
            pids = utils.execAndGetOutput(constants.EXT_PGREP + ' kksmd')
            if pids:
                self._pid = pids[0].strip()
                self.start()
            else:
                self._cif.log.error('failed to find kksmd thread')
        self.cpuUsage = 0

    def _getKsmdJiffies(self):
        return sum(map(int, file('/proc/%s/stat' % self._pid) \
                                    .read().split()[13:15]))

    def run(self):
        try:
            self.state, self.pages = self.readState()
            KSM_MONITOR_INTERVAL = 60
            jiff0 = self._getKsmdJiffies()
            while True:
                time.sleep(KSM_MONITOR_INTERVAL)
                jiff1 = self._getKsmdJiffies()
                self.cpuUsage = (jiff1 - jiff0) % 2**32 * 100 / \
                                os.sysconf('SC_CLK_TCK') / KSM_MONITOR_INTERVAL
                jiff0 = jiff1
                self.adjust()
        except:
            self._cif.log.error(traceback.format_exc())

    def readState(self):
        cmd = [self._prog, 'info']
        p = subprocess.Popen(cmd, close_fds=True,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE)
        out, err = p.communicate()
        try:
            dummy, flags, dummy, npages, dummy, dummy = out.split()
            state = int(flags[:-1]) & 1 == 1
            npages = int(npages[:-1])
            return state, npages
        except:
            return False, 0

    def _increasePages(self, npages_delta):
        KSM_NPAGES_MAX = config.getint('ksm', 'ksm_npages_max')
        KSM_NPAGES_MIN = config.getint('ksm', 'ksm_npages_min')

        self.pages = max(min(self.pages + npages_delta,
                KSM_NPAGES_MAX), KSM_NPAGES_MIN)

    def adjust(self):
        """adjust ksm state according to configuration and current memory stress
        return whether ksm is running"""
        KSM_NPAGES_BOOST = config.getint('ksm', 'ksm_npages_boost')
        KSM_NPAGES_DECAY = config.getint('ksm', 'ksm_npages_decay')

        ksmEnabled = config.getboolean('ksm', 'ksm_enable')
        if not ksmEnabled:
            return False
        self._lock.acquire()
        try:
            memTotal, memFree, memCached = utils.readTotalFreeCachedMem()
            memFree += memCached
            memFree *= 1024
            memTotal *= 1024
            memCommitted = self._cif._memCommitted() # XXX private!
            coef = config.getfloat('ksm', 'ksm_thres_coef') / 100
            const = config.getint('ksm', 'ksm_thres_const') * 2**20
            thres = max(const, coef * memTotal)
            ksmEnabled = ksmEnabled and (memCommitted + thres > memTotal)
            if self.state == ksmEnabled == False:
                return ksmEnabled
            if ksmEnabled:
                if memFree < thres:
                    self._increasePages(KSM_NPAGES_BOOST)
                else:
                    self._increasePages(KSM_NPAGES_DECAY)
                cmd = "%s start %s %s" % (self._prog, self.pages, self._sleep)
            else:
                cmd = "%s stop" % self._prog
            self._cif.log.debug('%s: committed %s, free %s, total %s' %
                            (cmd, memCommitted, memFree, memTotal))
            subprocess.call(cmd, close_fds=True, shell=True)
            self.state = ksmEnabled
            return ksmEnabled
        finally:
            self._lock.release()
